from django.core.paginator import Paginator,EmptyPage
from django.shortcuts import render, redirect
from django.views import View
from django import http
from django.db import DatabaseError
from django.urls import reverse
from django.contrib.auth import login, authenticate,logout
from django_redis import get_redis_connection
from django.contrib.auth.mixins import LoginRequiredMixin

from .utils import generate_verify_email_url,check_verify_email_token
from . import constants
import re, json, logging
from users.models import User,Address
from meiduo_mall.utils.views import LoginRequiredJSONMixin
from meiduo_mall.utils.response_code import RETCODE
from celery_tasks.email.tasks import send_verify_email
from goods.models import SKU
from carts.utils import merge_carts_cookie_redis
from orders.models import OrderInfo

# Create your views here.

# 生成日志器
logger = logging.getLogger('django')


class UserBrowseHistory(LoginRequiredJSONMixin,View):
    """用户浏览商品历史记录"""
    def post(self,request):
        """保存浏览记录"""

        json_str = request.body.decode()
        json_dict = json.loads(json_str)
        sku_id = json_dict.get('sku_id')

        # 校验参数
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('sku不存在')

        redis_conn = get_redis_connection('history')
        pl = redis_conn.pipeline()
        user = request.user
        # 先去重
        pl.lrem('history_%s' % user.id, 0, sku_id)
        # 再保存：最近浏览的商品在最前面
        pl.lpush('history_%s' % user.id, sku_id)
        # 最后截取
        pl.ltrim('history_%s' % user.id, 0, 4)
        # 执行
        pl.execute()

        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK'})

    def get(self, request):
        """查询用户商品浏览记录"""
        # 获取登录用户信息
        user = request.user
        # 创建连接到redis对象
        redis_conn = get_redis_connection('history')
        # 取出列表数据（核心代码）
        sku_ids = redis_conn.lrange('history_%s' % user.id, 0, -1) # (0, 4)

        # 将模型转字典
        skus = []
        for sku_id in sku_ids:
            sku = SKU.objects.get(id=sku_id)
            skus.append({
                'id': sku.id,
                'name': sku.name,
                'price': sku.price,
                'default_image_url': sku.default_image.url
            })

        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'skus': skus})


class ChangePasswordView(View):
    """修改密码"""

    def get(self,request):
        """展示修改密码界面"""
        return render(request,'user_center_pass.html')

    def post(self,request):
        """修改密码逻辑"""

        # 接受数据
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        new_password2 = request.POST.get('new_password2')

        # 校验参数
        if not all([old_password,new_password,new_password2]):
            return http.HttpResponseForbidden('缺少必传的参数')

        try:
            request.user.check_password(old_password)
        except Exception as e:
            logger.error(e)
            return render(request, 'user_center_pass.html',{'origin_pwd_errmsg':'原始密码错误'})

        if not re.match(r'^[0-9a-zA-Z]{8,20}$', new_password):
            return http.HttpResponseForbidden('密码最少8位，最多20位')

        if new_password != new_password2:
            return http.HttpResponseForbidden('两次密码输入不一致')

        # 修改密码
        try:
            request.user.password=new_password
            request.user.save()
        except Exception as e:
            logger.error(e)
            return render(request, "user_center_pass.html", {"change_pwd_errmsg": '修改密码失败'})

        # 清理状态保持
        logout(request)
        response = redirect(reverse('users:login'))
        response.delete_cookie('username')

        # 响应结果
        return response


class UpdateTitleAddressView(LoginRequiredJSONMixin, View):
    """设置地址标题"""

    def put(self, request, address_id):
        """设置地址标题"""
        # 接收参数：地址标题
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')

        try:
            # 查询地址
            address = Address.objects.get(id=address_id)

            # 设置新的地址标题
            address.title = title
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置地址标题失败'})

        # 4.响应删除地址结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置地址标题成功'})


class DefaultAddressView(LoginRequiredJSONMixin, View):
    """设置默认地址"""

    def put(self, request, address_id):
        """设置默认地址"""
        try:
            # 接收参数,查询地址
            address = Address.objects.get(id=address_id)

            # 设置地址为默认地址
            request.user.default_address = address
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '设置默认地址失败'})

        # 响应设置默认地址结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '设置默认地址成功'})


class UpdateDestoryAddressView(View):
    """更新和删除地址数据"""
    def put(self,request,address_id):
        """更新数据"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')
        # 用新数据覆盖旧数据
        try:
            address = Address.objects.filter(id=address_id)
            address.update(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )

        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '修改地址失败'})

        # 准备新的数据
        address = Address.objects.get(id=address_id)
        address_dict = {
            "id": address.id,
            "title": address.title,
            "receiver": address.receiver,
            "province": address.province.name,
            "city": address.city.name,
            "district": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email
        }
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '更新地址成功', 'address': address_dict})

    def delete(self,request,address_id):
        """删除地址"""
        # 实现指定地址的逻辑删除：is_delete=True
        try:
            address = Address.objects.get(id=address_id)
            address.is_deleted = True
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '删除地址失败'})

        # 响应结果：code, errmsg
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': '删除地址成功'})


class CreateAddressView(LoginRequiredJSONMixin,View):
    """新增地址"""
    def post(self, request):
        """实现新增地址逻辑"""

        # 判断用户地址数量是否超量，查询当前登录用户地址的数量
        # count = Address.objects.filter(user=reqeust.user).count()
        count = request.user.addresses.count() # 一查多，使用relate_name查询
        if count > constants.USER_ADDRESS_COUNTS_LIMIT:
            return http.JsonResponse({'code': RETCODE.THROTTLINGERR, 'errmsg':'超出地址数量上限'})
        # 接受参数,json数据在请求体中
        json_dict = json.loads(request.body.decode())
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')
        # 保存用户存入信息
        try:
            address = Address.objects.create(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email,
            )
            # 设置当前登录用户的，默认地址
            if request.user.default_address is None:
                request.user.default_address = address
                request.user.save()

        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR,'errmsg': '新增地址失败'})

        # 构造地址数据
        address_dict ={
            'id': address.id,
            'receiver':address.receiver,
            "title": address.title,
            "province": address.province.name,
            "city": address.city.name,
            "disrict": address.district.name,
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email,
        }
        # 响应新增地址结果：需要将新增的地址返回给前端渲染
        return http.JsonResponse( {'code': RETCODE.OK, 'errmsg': '新增地址成功', 'address': address_dict} )


class AddressView(LoginRequiredMixin,View):
    """用户收货地址"""
    def get(self, request):
        """查询登录用户信息并且展示地址"""

        # 获取当前登录用户信息
        login_user = request.user
        # 使用当前用户并且已is_delate=False为条件查询地址
        # addresses = Address.objects.filter(user=login_user,is_deleted=False)
        addresses = Address.objects.filter(user_id=login_user, is_deleted=False )
        # 将用户地址模型列表转字典列表:因为JsonResponse和Vue.js不认识模型类型，只有Django和Jinja2模板引擎认识
        address_list= []
        for address in addresses:
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
            address_list.append(address_dict)

        # 构造上下文
        context = {
            'default_address_id': login_user.default_address_id,
            'addresses': address_list
        }
        return render(request, 'user_center_site.html', context)


class VerifyEmailView(View):
    """验证邮箱"""

    def get(self, request):
        # 接收参数
        token = request.GET.get('token')

        # 校验参数
        if not token:
            return http.HttpResponseForbidden('缺少token')

        # 从token中提取用户信息user_id ==> user
        user = check_verify_email_token(token)
        if not user:
            return http.HttpResponseBadRequest('无效的token')

        # 将用户的email_active字段设置为True
        try:
            user.email_active = True
            user.save()
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('激活邮箱失败')

        # 响应结果：重定向到用户中心
        return redirect(reverse('users:info'))


class EmailView(LoginRequiredMixin,View):
    """添加邮箱"""
    def put(self,request):
        """实现业务逻辑"""
        # 接受参数
        # ① json_str = request.body.decode() 转换成字符串
        # ② 转换成字典，好提取email的值
        json_dict = json.loads(request.body.decode())
        email = json_dict.get('email')
        # 校验参数
        if not email:
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('参数有误')
        try:
            request.user.email = email
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code':RETCODE.EMAILERR,'errmsg':'添加邮箱失败'})
        # 发送验证邮件
        verify_url = generate_verify_email_url(request.user)
        send_verify_email.delay(email, verify_url)
        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg':'添加邮箱成功'})


class UserCenterInfo(LoginRequiredMixin, View):
    """用户中心页面展示"""
    def get(self,request):
        """①展现用户中心"""
        # if request.user.is_authenticated():
        #     return render(request,'user_center_info.html')
        # else:
        #     return redirect(reverse('users:login'))
        '''自己写的时候要看LoginRequiredMixin的官方源代码，新版的django用
            LoginRequiredMixin来限制用户的登陆
            '''
        # login_url='login/'  这是没有默认值的，可以在设置中配置地址
        # redirect_field_name='redirect_to'  这里有默认值next 就是哪里来回那里去
        # 所以这里直接导入loginrequiredmixin这个累就好，不用再写代码

        # ②提供个人信息页面，在中新页面上显示用户名手机好和邮箱
        # 如果LoginRequiredMixin,判断用户已经登入,那么request.user就是用户对象
        context={
            'username': request.user.username,
            'mobile': request.user.mobile,
            'email_active': request.user.email_active,
            'email': request.user.email,
        }

        return render(request,'user_center_info.html', context=context)


class LogoutView(View):
    """用户退出登陆"""
    def get(self,request):
        # 实现用户退出功能
        logout(request)
        # 退出后重定向到首页
        response = redirect(reverse('contents:index'))

        # 退出后删除session信息
        response.delete_cookie('username')
        return response


class LoginView(View):
    """登入接口"""
    def get(self,request):
        """
        提供登陆界面
        :param request:
        :return:返回登如页面
        """
        return render(request, "login.html")

    def post(self,request):
        """
        实现登陆逻辑
        :param request:
        :return:
        """
        # 接受参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get("remembered")
        # 校验参数
        # ①判断参数是否齐全
        if not all([username, password]):
            return http.HttpResponseForbidden('缺少必传参数')
        # ②判断用户名是否符合规矩
        if not re.match(r'^[a-zA-Z0-9_-]{5,10}$', username):
            return http.HttpResponseForbidden('请输入正确的手机号或者用户名') #这里报错两个参数错误是防止黑客恶意测试

        # ③判断密码是否8到20位
        if not re.match(r'^[0-9a-zA-Z]{8,20}$',password):
            return http.HttpResponseForbidden('密码最短8位，最长20位')

        # ④用户认证
        user = authenticate(request, username=username, password=password)
        if user is None:
            return render(request, 'login.html',{'account_errmsg':'用户名或密码错误'})
        # 实现状态保持
        login(request,user)

        # 设置状态保持的周期
        if remembered != "on":
            # 没有记住用户，在页面关闭的时候结束状态保持
            request.session.set_expiry(0)
        else:
            # 记住用户，实现状态保持，默认保存两周
            request.session.set_expiry(None)
        # 响应结果
        # 所有验证成功了，就重定向到主页面
        # response = redirect(reverse('contents:index'))

        next = request.GET.get('next')
        if next:
            response = redirect(next)
        else:
            response = redirect(reverse('contents:index'))

        # 保存session信息，为 后面登入成功能在页面显示用户准备
        response.set_cookie('username', user.username,max_age=3600*24*15)

        # 合并cookie和redis的购物车数据
        merge_carts_cookie_redis(user,request,response)
        # 响应结果
        return response


class MobileCount(View):
    """判断手机号是否重复注册"""
    def get(self,request,mobile):
        """

        :param request:
        :param mobile:
        :return:JSON
        """
        count = User.objects.filter(mobile=mobile).count()
        return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'OK', 'count': count})


class UsernameCountView(View):
    """判断用户名是否重复注册"""

    def get(self, request, username):
        """
        :param username: 用户名
        :return: JSON
        """
        # 实现主体业务逻辑：使用username查询对应的记录的条数(filter返回的是满足条件的结果集)
        count = User.objects.filter(username=username).count()
        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class RegisterView(View):
    """用户注册"""

    def get(self, request):
        """提供用户注册页面"""
        return render(request, 'register.html')

    def post(self, request):
        """实现用户注册业务逻辑"""
        # 接收参数：表单参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        allow = request.POST.get('allow')
        sms_code_client = request.POST.get('sms_code')

        # 校验参数：前后端的校验需要分开，避免恶意用户越过前端逻辑发请求，要保证后端的安全，前后端的校验逻辑相同
        # 判断参数是否齐全:all([列表])：会去校验列表中的元素是否为空，只要有一个为空，返回false
        if not all([username, password, password2, mobile, allow]):
            return http.HttpResponseForbidden('缺少必传参数')
        # 判断用户名是否是5-20个字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden('请输入5-20个字符的用户名')
        # 判断密码是否是8-20个数字
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            return http.HttpResponseForbidden('请输入8-20位的密码')
        # 判断两次密码是否一致
        if password != password2:
            return http.HttpResponseForbidden('两次输入的密码不一致')
        # 判断手机号是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('请输入正确的手机号码')
        redis_conn = get_redis_connection('verify_code')
        sms_code_server = redis_conn.get('sms_%s'% mobile)
        # 由于保存的数据肯呢个会失效所以要进行判断是否失效
        if sms_code_server is None:
            return render(request, 'register.html',{'sms_code_errmsg': '输入短信验证码已失效'})
        if sms_code_client != sms_code_server.decode():
            return render(request, 'register.html', {'sms_code_errmsg': '输入短信验证码错误'})
        # 判断是否勾选用户协议
        if allow != 'on':
            return http.HttpResponseForbidden('请勾选用户协议')

        # 保存注册数据：是注册业务的核心
        # return render(request, 'register.html', {'register_errmsg': '注册失败'})
        try:
            user = User.objects.create_user(username=username, password=password, mobile=mobile)
        except DatabaseError:
            return render(request, 'register.html', {'register_errmsg':'注册失败'})

        # 实现状态保持
        login(request, user)

        # 响应结果：重定向到首页
        # return http.HttpResponse('注册成功，重定向到首页')
        # return redirect('/')
        # reverse('contents:index') == '/'
        return redirect(reverse('contents:index'))